package com.worley.argutils.config;

import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.util.StringUtils;

import com.worley.argutils.config.RedisProperties.Cluster;

import redis.clients.jedis.JedisPoolConfig;

public class RedisOpsConnectionFactory {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    public static final String PROP_PREFIX = "spring.redis.";

    private final Map<String, RedisProperties> propMap;
    private Map<String, Map<Integer, JedisConnectionFactory>> rcfMap = new HashMap<>();

    /**
     * 初始化每个系统的Redis连接配置
     */
    public RedisOpsConnectionFactory(
            ConfigurableListableBeanFactory beanFactory) {
        List<String> configKey = new LinkedList<>();
        setRedisConfigKey(null, RedisProperties.class, configKey);

        Map<String, RedisProperties> tempPropMap = new HashMap<>();

        for (CacheRegion cr : CacheRegion.values()) {
            try {
                configKey.forEach(k -> {
                    String key;
                    if (cr == CacheRegion.NONE) {
                        key = String.format("${%s%s}", PROP_PREFIX, k);
                    } else {
                        key = String.format("${%s%s.%s}", PROP_PREFIX,
                                cr.name().toLowerCase(), k);
                    }
                    String value;
                    try {
                        value = beanFactory.resolveEmbeddedValue(key);
                    } catch (IllegalArgumentException e) {
                        value = key;
                    }
                    if (value.equals(key)) {
                        log.debug("属性未配置：{}", key);
                    } else {
                        RedisProperties prop = tempPropMap.computeIfAbsent(cr.name(),
                                regionKey -> new RedisProperties());
                        setProperty(k, value, prop);
                    }
                });
            } catch (Exception e) {
                log.error("属性设置异常：{}={}", cr, tempPropMap.get(cr.name()), e);
            }
        }

        propMap = Collections.unmodifiableMap(tempPropMap);
    }

    // 读取配置属性列表
    private static void setRedisConfigKey(String key, Class<?> prop,
            List<String> configKey) {
        for (Field fd : prop.getDeclaredFields()) {
            String kn;
            if (key == null) {
                kn = String.format("%s", fd.getName());
            } else {
                kn = String.format("%s.%s", key, fd.getName());
            }
            if (fd.getType().getName().contains("sekorm")) {
                setRedisConfigKey(kn, fd.getType(), configKey);
            } else {
                configKey.add(kn);
            }
        }
    }

    // 设置redis相关配置属性
    private void setProperty(String key, String val, Object prop) {
        try {
            Class<?> clazz = prop.getClass();
            int index = key.indexOf('.');

            if (index > 0) {
                Field parentField = clazz
                        .getDeclaredField(key.substring(0, index));
                parentField.setAccessible(true);
                Object parentObj = parentField.get(prop);
                if (parentObj == null) {
                    parentObj = parentField.getType().newInstance();
                    parentField.set(prop, parentObj);
                }
                setProperty(key.substring(index + 1), val, parentObj);
            } else {
                Field field = clazz.getDeclaredField(key);
                field.setAccessible(true);
                Class<?> fieldType = field.getType();
                if (fieldType == String.class) {
                    field.set(prop, val);
                } else if (fieldType == int.class) {
                    field.set(prop, Integer.parseInt(val));
                } else if (fieldType == List.class) {
                    field.set(prop, Arrays.asList(val.split(",")));
                } else {
                    log.warn("不支持的属性类型:`{}`, {}={}", fieldType, key, val);
                }
            }
        } catch (Exception e) {
            log.error("属性设置异常：{}={}", key, val, e);
        }
    }

    // 根据配置创建Redis连接工厂
    public JedisConnectionFactory redisConnectionFactory(
            RedisProperties properties) {
        JedisConnectionFactory factory = createJedisConnectionFactory(
                properties);

        // 配置连接
        if (StringUtils.hasText(properties.getUrl())) {
            configureConnectionFromUrl(factory, properties);
        } else {
            factory.setHostName(properties.getHost());
            factory.setPort(properties.getPort());
            if (properties.getPassword() != null) {
                factory.setPassword(properties.getPassword());
            }
        }

        // 选择初始DB
        factory.setDatabase(properties.getDatabase());
        if (properties.getTimeout() > 0) {
            factory.setTimeout(properties.getTimeout());
        }
        factory.afterPropertiesSet();
        return factory;
    }

    private void configureConnectionFromUrl(JedisConnectionFactory factory,
            RedisProperties properties) {
        String url = properties.getUrl();
        try {
            URI uri = new URI(url);
            factory.setHostName(uri.getHost());
            factory.setPort(uri.getPort());
            if (uri.getUserInfo() != null) {
                String password = uri.getUserInfo();
                int index = password.indexOf(':');
                if (index >= 0) {
                    password = password.substring(index + 1);
                }
                factory.setPassword(password);
            }
        } catch (URISyntaxException ex) {
            throw new IllegalArgumentException(
                    "Malformed 'spring.redis.url' " + url, ex);
        }
    }

    // 根据配置创建Jedis连接工厂
    private JedisConnectionFactory createJedisConnectionFactory(
            RedisProperties properties) {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        if (properties.getPool() != null) {
            RedisProperties.Pool props = properties.getPool();
            poolConfig.setMaxTotal(props.getMaxActive());
            poolConfig.setMaxIdle(props.getMaxIdle());
            poolConfig.setMinIdle(props.getMinIdle());
            poolConfig.setMaxWaitMillis(props.getMaxWait());
        }

        RedisClusterConfiguration clusterConfig = getClusterConfiguration(
                properties);
        if (clusterConfig != null) {
            return new JedisConnectionFactory(clusterConfig, poolConfig);
        }
        return new JedisConnectionFactory(poolConfig);
    }

    private final RedisClusterConfiguration getClusterConfiguration(
            RedisProperties properties) {
        if (properties.getCluster() == null) {
            return null;
        }
        Cluster clusterProperties = properties.getCluster();
        RedisClusterConfiguration config = new RedisClusterConfiguration(
                clusterProperties.getNodes());

        if (clusterProperties.getMaxRedirects() != null) {
            config.setMaxRedirects(clusterProperties.getMaxRedirects());
        }
        return config;
    }

    /**
     * 根据配置获取Redis连接工厂
     */
    public RedisConnectionFactory getRedisConnectionFactory(CacheRegion cr,
            int db) {
        String region = cr.name();
        RedisProperties rp = propMap.get(region);
        if (rp == null) {
            throw new RedisConnectionFailureException("Redis连接标识未配置：" + cr);
        }
        if (rp.getCluster() != null && db != 0) {
            db = 0;
            log.warn("`{}`是Redis集群配置，只能选择db0", cr);
        }

        Map<Integer, JedisConnectionFactory> cfs = rcfMap
                .computeIfAbsent(region, k -> new HashMap<>());
        JedisConnectionFactory factory = cfs.get(Integer.valueOf(db));
        if (factory == null) {
            rp.setDatabase(db);
            factory = redisConnectionFactory(rp);
            cfs.put(db, factory);
        }
        return factory;
    }
}
